/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil ; -*- */
/*
 *  (C) 2001 by Argonne National Laboratory.
 *      See COPYRIGHT in top-level directory.
 */

/* Allow fprintf to logfile */
/* style: allow:fprintf:1 sig:0 */

/* Utility functions associated with PMI implementation, but not part of
   the PMI interface itself.  Reading and writing on pipes, signals, and parsing
   key=value messages
   */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include "simple_pmiutil.h"

/* Use the memory definitions from mpich/src/include */

#define MAXVALLEN 1024
#define MAXKEYLEN 32

/* These are not the keyvals in the keyval space that is part of the
   PMI specification.
   They are just part of this implementation's internal utilities.
   */
struct PMIU_keyval_pairs {
  char key[MAXKEYLEN];
  char value[MAXVALLEN];
};
static struct PMIU_keyval_pairs PMIU_keyval_tab[64] = {{{0}, {0}}};
static int PMIU_keyval_tab_idx = 0;

/* This is used to prepend printed output.  Set the initial value to
   "unset" */
static char PMIU_print_id[PMIU_IDSIZE] = "unset";

void PMIU_Set_rank(int PMI_rank)
{
  snprintf(PMIU_print_id, PMIU_IDSIZE, "cli_%d", PMI_rank);
}
void PMIU_SetServer(void)
{
  MPI1U_Strncpy(PMIU_print_id, "server", PMIU_IDSIZE);
}

/* Note that vfprintf is part of C89 */

/* style: allow:fprintf:1 sig:0 */
/* style: allow:vfprintf:1 sig:0 */
/* This should be combined with the message routines */
void PMIU_printf(int print_flag, const char* fmt, ...)
{
  va_list ap;
  static FILE* logfile = 0;

  /* In some cases when we are debugging, the handling of stdout or
     stderr may be unreliable.  In that case, we make it possible to
     select an output file. */
  if (!logfile) {
    char* p;
    p = getenv("PMI_USE_LOGFILE");
    if (p) {
      char filename[1024];
      p = getenv("PMI_ID");
      if (p) {
        snprintf(filename, sizeof(filename), "testclient-%s.out", p);
        logfile = fopen(filename, "w");
      } else {
        logfile = fopen("testserver.out", "w");
      }
    } else
      logfile = stderr;
  }

  if (print_flag) {
    /* error_printf( "[%s]: ", PMIU_print_id ); */
    /* FIXME: Decide what role PMIU_printf should have (if any) and
       select the appropriate MPIU routine */
    fprintf(logfile, "[%s]: ", PMIU_print_id);
    va_start(ap, fmt);
    vfprintf(logfile, fmt, ap);
    va_end(ap);
    fflush(logfile);
  }
}

#define MAX_READLINE 1024
/*
 * Return the next newline-terminated string of maximum length maxlen.
 * This is a buffered version, and reads from fd as necessary.  A
 */
int PMIU_readline(int fd, char* buf, int maxlen)
{
  static char readbuf[MAX_READLINE];
  static char *nextChar = 0, *lastChar = 0; /* lastChar is really one past
                                               last char */
  static int lastfd = -1;
  ssize_t n;
  int curlen;
  char *p, ch;

  /* Note: On the client side, only one thread at a time should
     be calling this, and there should only be a single fd.
     Server side code should not use this routine (see the
     replacement version in src/pm/util/pmiserv.c) */
  if (nextChar != lastChar && fd != lastfd) {
    // internal_error_printf( "Panic - buffer inconsistent\n" );
    return -1;
  }

  p = buf;
  curlen = 1; /* Make room for the null */
  while (curlen < maxlen) {
    if (nextChar == lastChar) {
      lastfd = fd;
      do {
        n = read(fd, readbuf, sizeof(readbuf) - 1);
      } while (n == -1 && errno == EINTR);
      if (n == 0) {
        /* EOF */
        break;
      } else if (n < 0) {
        /* Error.  Return a negative value if there is no
           data.  Save the errno in case we need to return it
           later. */
        if (curlen == 1) {
          curlen = 0;
        }
        break;
      }
      nextChar = readbuf;
      lastChar = readbuf + n;
      /* Add a null at the end just to make it easier to print
         the read buffer */
      readbuf[n] = 0;
      /* FIXME: Make this an optional output */
      /* printf( "Readline %s\n", readbuf ); */
    }

    ch = *nextChar++;
    *p++ = ch;
    curlen++;
    if (ch == '\n') break;
  }

  /* We null terminate the string for convenience in printing */
  *p = 0;

  /* Return the number of characters, not counting the null */
  return curlen - 1;
}

int PMIU_writeline(int fd, char* buf)
{
  ssize_t size, n;

  size = strlen(buf);
  if (size > PMIU_MAXLINE) {
    buf[PMIU_MAXLINE - 1] = '\0';
    PMIU_printf(1, "write_line: message string too big: :%s:\n", buf);
  } else if (buf[strlen(buf) - 1] != '\n') /* error:  no newline at end */
    PMIU_printf(1, "write_line: message string doesn't end in newline: :%s:\n",
                buf);
  else {
    do {
      n = write(fd, buf, size);
    } while (n == -1 && errno == EINTR);

    if (n < 0) {
      PMIU_printf(1, "write_line error; fd=%d buf=:%s:\n", fd, buf);
      perror("system msg for write_line failure ");
      return (-1);
    }
    if (n < size) PMIU_printf(1, "write_line failed to write entire message\n");
  }
  return 0;
}

/*
 * Given an input string st, parse it into internal storage that can be
 * queried by routines such as PMIU_getval.
 */
int PMIU_parse_keyvals(char* st)
{
  char *p, *keystart, *valstart;
  int offset;

  if (!st) return (-1);

  PMIU_keyval_tab_idx = 0;
  p = st;
  while (1) {
    while (*p == ' ') p++;
    /* got non-blank */
    if (*p == '=') {
      PMIU_printf(1,
                  "PMIU_parse_keyvals:  unexpected = at character %d in %s\n",
                  p - st, st);
      return (-1);
    }
    if (*p == '\n' || *p == '\0') return (0); /* normal exit */
    /* got normal character */
    keystart = p; /* remember where key started */
    while (*p != ' ' && *p != '=' && *p != '\n' && *p != '\0') p++;
    if (*p == ' ' || *p == '\n' || *p == '\0') {
      PMIU_printf(1,
                  "PMIU_parse_keyvals: unexpected key delimiter at character "
                  "%d in %s\n",
                  p - st, st);
      return (-1);
    }
    /* Null terminate the key */
    *p = 0;
    /* store key */
    MPI1U_Strncpy(PMIU_keyval_tab[PMIU_keyval_tab_idx].key, keystart,
                  MAXKEYLEN);

    valstart = ++p; /* start of value */
    while (*p != ' ' && *p != '\n' && *p != '\0') p++;
    /* store value */
    MPI1U_Strncpy(PMIU_keyval_tab[PMIU_keyval_tab_idx].value, valstart,
                  MAXVALLEN);
    offset = (int)(p - valstart);
    /* When compiled with -fPIC, the pgcc compiler generates incorrect
       code if "p - valstart" is used instead of using the
       intermediate offset */
    PMIU_keyval_tab[PMIU_keyval_tab_idx].value[offset] = '\0';
    PMIU_keyval_tab_idx++;
    if (*p == ' ') continue;
    if (*p == '\n' || *p == '\0') return (0); /* value has been set to empty */
  }
}

void PMIU_dump_keyvals(void)
{
  int i;
  for (i = 0; i < PMIU_keyval_tab_idx; i++)
    PMIU_printf(1, "  %s=%s\n", PMIU_keyval_tab[i].key,
                PMIU_keyval_tab[i].value);
}

char* PMIU_getval(const char* keystr, char* valstr, int vallen)
{
  int i, rc;

  for (i = 0; i < PMIU_keyval_tab_idx; i++) {
    if (strcmp(keystr, PMIU_keyval_tab[i].key) == 0) {
      rc = MPI1U_Strncpy(valstr, PMIU_keyval_tab[i].value, vallen);
      if (rc != 0) {
        PMIU_printf(1, "MPI1U_Strncpy failed in PMIU_getval\n");
        return NULL;
      }
      return valstr;
    }
  }
  valstr[0] = '\0';
  return NULL;
}

void PMIU_chgval(const char* keystr, char* valstr)
{
  int i;

  for (i = 0; i < PMIU_keyval_tab_idx; i++) {
    if (strcmp(keystr, PMIU_keyval_tab[i].key) == 0) {
      MPI1U_Strncpy(PMIU_keyval_tab[i].value, valstr, MAXVALLEN - 1);
      PMIU_keyval_tab[i].value[MAXVALLEN - 1] = '\0';
    }
  }
}

/*
 * MPI1U_Strncpy - Copy at most n characters.  Stop once a null is reached.
 *
 * This is different from strncpy, which null pads so that exactly
 * n characters are copied.  The strncpy behavior is correct for many
 * applications because it guarantees that the string has no uninitialized
 * data.
 *
 * If n characters are copied without reaching a null, return an error.
 * Otherwise, return 0.
 *
 * Question: should we provide a way to request the length of the string,
 * since we know it?
 */
/*@ MPI1U_Strncpy - Copy a string with a maximum length

Input Parameters:
+   instr - String to copy
-   maxlen - Maximum total length of 'outstr'

Output Parameters:
.   outstr - String to copy into

    Notes:
    This routine is the routine that you wish 'strncpy' was.  In copying
    'instr' to 'outstr', it stops when either the end of 'outstr' (the
    null character) is seen or the maximum length 'maxlen' is reached.
    Unlike 'strncpy', it does not add enough nulls to 'outstr' after
    copying 'instr' in order to move precisely 'maxlen' characters.
    Thus, this routine may be used anywhere 'strcpy' is used, without any
    performance cost related to large values of 'maxlen'.

    If there is insufficient space in the destination, the destination is
    still null-terminated, to avoid potential failures in routines that neglect
    to check the error code return from this routine.

  Module:
  Utility
  @*/
int MPI1U_Strncpy(char* dest, const char* src, size_t n)
{
  char* restrict d_ptr = dest;
  const char* restrict s_ptr = src;
  register int i;

  if (n == 0) return 0;

  i = (int)n;
  while (*s_ptr && i-- > 0) {
    *d_ptr++ = *s_ptr++;
  }

  if (i > 0) {
    *d_ptr = 0;
    return 0;
  } else {
    /* Force a null at the end of the string (gives better safety
       in case the user fails to check the error code) */
    dest[n - 1] = 0;
    /* We may want to force an error message here, at least in the
       debugging version */
    /*printf( "failure in copying %s with length %d\n", src, n ); */
    return 1;
  }
}

/* Append src to dest, but only allow dest to contain n characters (including
   any null, which is always added to the end of the line */
/*@ MPIU_Strnapp - Append to a string with a maximum length

Input Parameters:
+   instr - String to copy
-   maxlen - Maximum total length of 'outstr'

Output Parameters:
.   outstr - String to copy into

    Notes:
    This routine is similar to 'strncat' except that the 'maxlen' argument
    is the maximum total length of 'outstr', rather than the maximum
    number of characters to move from 'instr'.  Thus, this routine is
    easier to use when the declared size of 'instr' is known.

  Module:
  Utility
  @*/
int MPIU_Strnapp(char* dest, const char* src, size_t n)
{
  char* restrict d_ptr = dest;
  const char* restrict s_ptr = src;
  register int i;

  /* Get to the end of dest */
  i = (int)n;
  while (i-- > 0 && *d_ptr) d_ptr++;
  if (i <= 0) return 1;

  /* Append.  d_ptr points at first null and i is remaining space. */
  while (*s_ptr && i-- > 0) {
    *d_ptr++ = *s_ptr++;
  }

  /* We allow i >= (not just >) here because the first while decrements
     i by one more than there are characters, leaving room for the null */
  if (i >= 0) {
    *d_ptr = 0;
    return 0;
  } else {
    /* Force the null at the end */
    *--d_ptr = 0;

    /* We may want to force an error message here, at least in the
       debugging version */
    return 1;
  }
}
